from logging import getLogger
from copy import deepcopy
from urllib2 import urlopen
from sets import Set

from persistent import Persistent

from zope.interface import implements
from zope.component import getUtility

import solr

try:
    from xml.etree import cElementTree as etree
except ImportError:
    from elementtree import ElementTree as etree

from meercat.ui.plone.interfaces import IMeercatConnectionConfig
from meercat.ui.plone.interfaces import IMeercatConnectionManager

try:
    from threading import local
except ImportError:
    from dummy_threading import local



log = getLogger('meercat.ui.plone.connection')

_locals = local()

def hasLocal(key):
    return hasattr(_locals,key)

def getLocal(key, marker=None):
    return getattr(_locals,key,marker)

def setLocal(key, value):
    return setattr(_locals, key, value)

setLocal('solr',None)
setLocal('rest',None)

def unicodeStringEncoding(value):
    try:
        if isinstance(value, basestring):
            return value.encode('utf-8')
    except UnicodeDecodeError:
        try:
            return value.decode('utf-8').encode('utf-8')
        except:
            return value
    
    return value
        

def unicodeParameterEncoding(params):
    for k,v in params.iteritems():
            if isinstance(v,basestring):
                params[k] = unicodeStringEncoding(v)
            elif isinstance(v, list):
                params[k] = []
                for sv in v:
                    params[k].append(unicodeStringEncoding(sv))
    
    return params
                    

class MeercatConnectionConfig(Persistent):
    """ utility to hold the connection configuration for the meercat server """
    implements(IMeercatConnectionConfig)
    
    def __init__(self):
        self.enabled = False
        self.solr_enabled = False
        self.solr_url = None
        self.solr_ssl_key_file = None
        self.solr_ssl_cert_file = None
        self.solr_timeout = 0
        self.solr_default_parameters = {}
        self.solr_forced_parameters = {}
        self.rest_enabled = False
        self.rest_url = None
        self.rest_timeout = 0
    
    def getId(self):
        """ return a unique id to be used with GenericSetup """
        return 'meercat'

def parse_rest_response(response):
    resource = etree.parse(response).getroot()
    log.debug('parsing a response: %s' % (response,))
    log.debug('parsing a resource: %s' % (resource,))
    output = {}
    output['instances'] = []
    
    for child in resource.getchildren():
        log.debug('parsing: %s' % (child,))
        if child.tag in ('instance', 'instances',):
            output['instances'].append(parse_rest_instance(child))
            continue
        if child.tag == 'url':
            text = parse_rest_url(child)
        else:
            text = clean_rest_value(child.text)
        
        if text:
            output.setdefault(child.tag, Set()).add(text)
    
    log.debug('parsed response to: %s' % (str(output),))
    
    return output 

def parse_rest_instance(instance):
    output = {}
    output['holdings'] = []
    
    log.debug('parsing an instance: %s' % (instance,))
    
    for child in instance.getchildren():
        log.debug('parsing: %s' % (child,))
        if child.tag in ('holding','holdings'):
            output['holdings'].append(parse_rest_holding(child))
            continue
        
        if child.tag == 'location':
            parse_rest_location_code(child, output)
        
        if child.tag == 'url':
            text = parse_rest_url(child)
        else:
            text = clean_rest_value(child.text)
        
        if text:
            output.setdefault(child.tag, Set()).add(text)
    
    return output

def parse_rest_holding(holding):
    output = {}
    log.debug('parsing a holding: %s' % (holding,))
    for child in holding.getchildren():
        log.debug('parsing: %s' % (child,))
        if child.tag in ('copy','copies',):
            output.setdefault('copies',[]).append(parse_rest_copy(child))
            continue
        
        if child.tag == 'location':
            parse_rest_location_code(child, output)
        
        text = clean_rest_value(child.text)
        title = clean_rest_value(child.get('title'))
        if title and text:
            value = (title, text,)
        elif text:
            value = text
        else:
            continue
        output.setdefault(child.tag, Set()).add(value)
    
    output['type'] = holding.get('type')
    output['available'] = holding.get('available').lower() in ('true','yes','1')
    
    if 'copies' in output:
        output['copies'].sort(key=lambda c: ','.join(c.get('threshold',[])))
    
    if len(output) <= 2:
        return None
    
    return output

def parse_rest_copy(copy):
    output = {}
    log.debug('parsing a copy: %s' % (copy,))
    for child in copy.getchildren():
        text = clean_rest_value(child.text)
        
        if child.tag == 'barcode' and child.get('status').lower() != 'active':
            continue
        
        if text:
            output.setdefault(child.tag, Set()).add(text)
        
        if child.tag == 'location':
            parse_rest_location_code(child, output)
    
    output = dict(map(lambda t: (t[0],list(t[1])), output.iteritems()))
    
    if 'threshold' in output:
        output['threshold'].sort()
    
    return output

def parse_rest_location_code(location, output={}):
    if location.get('code'):
        text = clean_rest_value(location.get('code'))
        if text:
            output.setdefault('locationCode', Set()).add(text)
    return output

def parse_rest_url(url):
    if url.tag == 'url':
        text = clean_rest_value(url.text)
        if url.get('title') and text:
            return (url.get('title'),text,)
        elif text:
            return text
    return None

def clean_rest_value(value):
    if not value:
        return
    return value.replace('\t','').strip()

class MeercatConnectionManager(object):
    """ a thread-local connection manager for meercat """
    implements(IMeercatConnectionManager)

    def __init__(self):
        pass
    
    def _getConfig(self):
        return getUtility(IMeercatConnectionConfig)
    
    def enable(self):
        self._getConfig().enabled = True
    
    def disable(self):
        self._getConfig().enabled = False
    
    def configureSolr(self, **kwargs):
        """ set solr configuration, only pass in parameters that should change """
        config = self._getConfig()
        
        for key, value in kwargs.iteritems():
            if hasattr(config, 'solr_%s' % (str(key),)):
                setattr(config, 'solr_%s' % (str(key),), value)
            else:
                raise AttributeError('%s is not a valid solr configuration parameter' % (str(key),))

        self.closeSolrConnection()
    
    def configureRest(self, **kwargs):
        config = self._getConfig()
        
        for key, value in kwargs.iteritems():
            if hasattr(config, 'rest_%s' % (str(key),)):
                setattr(config, 'rest_%s' % (str(key),), value)
            else:
                raise AttributeError('%s is not a valid rest configuration parameter' % (str(key),))
        
        self.closeRestConnection()
    
    def closeSolrConnection(self):
        """ close the current connection, if any """
        log.debug('closing connection')
        conn = getLocal('solr',None)
        if conn is not None:
            conn.close()
            setLocal('solr',None)
    
    def getSolrConnection(self):
        """ returns an existing connection or opens one """
        config = self._getConfig()
        
        if not config.enabled or not config.solr_enabled:
            return None
        
        conn = getLocal('solr',None)
        
        if conn is None and config.solr_url is not None:
            conn = solr.SolrConnection(url=config.solr_url)
            setLocal('solr',conn)
        
        return conn
    
    def closeRestConnection(self):
        """ close the current rest connection, if any """
        log.debug('closing connection')
        conn = getLocal('rest',None)
        if conn is not None:
            conn.close()
            setLocal('rest',None)
    
    def getRestConnection(self):
        """ return an existing rest connection or opens one and returns it """
        return None
    
    def getIdentifierField(self):
        """ return the primary identifier field from SOLR """
        return u'id'
    
    def getSolrVersion(self):
        """ return the version of SOLR from the configured server """
        return 1.4
    
    def _query(self, **params):
        log.debug(u'query parameters: %s' % (unicode(params)))
        conn = self.getSolrConnection()
        results = conn.query(**unicodeParameterEncoding(params))
        if results.numFound > 0:
            results.header['params'] = unicodeParameterEncoding(results.header['params'])
            results.paginator = solr.SolrPaginator(results)
        else:
            log.info('No results found')
        return results
    
    def query(self, **params):
        if not self.getSolrConnection():
            log.warning('The Meercat SOLR connection has not been enabled')
            return None
        
        config = self._getConfig()
        qp = {}
        # Add all default parameters in
        qp.update(config.solr_default_parameters)
        
        # Add user code submitted parameters
        qp.update(params)
        
        # Add forced query parameters
        qp.update(config.solr_forced_parameters)
        
        worked = False
        count = 0
        
        while not worked and count < 3:
            try:
                return self._query(**qp)
            except Exception, e:
                log.warning('Exception while querying solr: %s' % (str(e)))
                count += 1
        
        log.error('Unable to query SOLR')
    
    def getResource(self, identifier):
        config = self._getConfig()
        
        if not config.enabled or not config.rest_enabled:
            return None
        
        url = '%s/%s/' % (config.rest_url.rstrip('/'),
                          str(identifier).replace('/','_'),)
        
        log.debug('getResource: %s' % (url,))
        return parse_rest_response(urlopen(url))